package com.wenbing.miio.mi.device.remote;

import com.wenbing.miio.core.*;
import com.wenbing.miio.core.exception.NetworkLayerException;
import com.wenbing.miio.jsonrpc.JsonRpcMessage;
import com.wenbing.miio.mi.MiCodecLayer;
import com.wenbing.miio.mi.MiDeviceUtil;
import com.wenbing.miio.mi.MiFrameLayer;

import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;


/**
 * @author wen bing
 * @projectName: miio
 * @package: com.wenbing.miio
 * @className: Device
 * @date 2024/09/19 15:41
 **/
public class RemoteMiDevice extends Device {

    private SocketAddress remoteAddress;

    private MiClient client;

    public RemoteMiDevice(HardwareConfiguration hardwareConfiguration, SocketAddress remoteAddress) throws SocketException {
        super(hardwareConfiguration, new RemoteClock(), new DatagramSocketNetworkInterface());
        this.remoteAddress = remoteAddress;
        NetworkInterface remoteNetworkInterface = new ProxyNetworkInterface(this.localNetworkInterface, remoteAddress);
        this.client = new MiClient(this::updateClock, remoteNetworkInterface);
    }

    public void connect() throws IOException, InterruptedException {
        this.client.hello();
    }

    public void disconnect() {

    }

    public Object info() throws IOException, InterruptedException {
        return client.syncRequest("miIO.info", null);
    }

    public Object getProperty(int siid, int piid) throws IOException, InterruptedException {
        Map<String, Object> parameter = buildParameter(siid, piid, null);
        JsonRpcMessage message = client.syncRequest("get_properties", parameter);
        if (message.getError() == null) {
            return message.getResult();
        }
        throw new IOException(message.getMethod() + "  " + message.getError().getMessage());
    }

    public Object setProperty(int siid, int piid, Object... value) throws IOException, InterruptedException {
        Map<String, Object> parameter = buildParameter(siid, piid, null);
        parameter.put("value", value);
        JsonRpcMessage message = client.syncRequest("set_properties", parameter);
        System.out.println(message);
        return null;
    }

    public Object action(int siid, int aiid, Object... in) throws IOException, InterruptedException {
        Map<String, Object> parameter = buildParameter(siid, null, aiid);
        parameter.put("in", in);
        JsonRpcMessage message = client.syncRequest("action", parameter);
        System.out.println(message);
        return null;
    }

    private Map<String, Object> buildParameter(int siid, Integer piid, Integer aiid) {
        Map<String, Object> map = new HashMap<>();
        map.put("did", MiDeviceUtil.getDeviceId(getHardwareConfiguration()));
        map.put("siid", siid);
        if (piid != null)
            map.put("piid", piid);
        if (aiid != null)
            map.put("aiid", aiid);
        return map;
    }
    @Override
    public void initNetworkStack(NetworkStack stack) {
        StacksAdapterNetworkLayer layer = new StacksAdapterNetworkLayer() {
            @Override
            protected void inboundContextCopy(NetworkLayerContext from, NetworkLayerContext to) {
                Context.copy(from, to, true);
            }

            @Override
            protected void outboundContextCopy(NetworkLayerContext from, NetworkLayerContext to) {
                Context.copy(from, to, true);
            }

            @Override
            protected ByteBuffer unpacking(NetworkLayerContext context, ByteBuffer packingData) throws IOException, NetworkLayerException {
                return packingData;
            }

            @Override
            protected ByteBuffer packing(NetworkLayerContext context, ByteBuffer packingData) throws IOException, NetworkLayerException {
                return packingData;
            }
        };

        SimpleNetworkStack simpleNetworkStack = new SimpleNetworkStack(stack.getConfiguration(), stack.getClock());
        layer.registerChildNetworkStackLayer(simpleNetworkStack);
        simpleNetworkStack.addLowerLayer(new MiFrameLayer());
        simpleNetworkStack.addLowerLayer(new MiCodecLayer());
        simpleNetworkStack.addLowerLayer(new ClientModeAdapterLayer());
        /*stack.addLowerLayer(new MiFrameLayer());
        stack.addLowerLayer(new MiCodecLayer());
        stack.addLowerLayer(new ClientModeAdapterLayer());*/
        stack.addLowerLayer(layer);
    }

    @Override
    public void boot() {
        super.boot();
        this.client.boot();
    }

    private void updateClock(NetworkLayerContext context) {
        if (getClock() instanceof RemoteClock remoteClock) {
            long timestamp = context.getAttribute(MiFrameLayer.ATTR_TIMESTAMP);
            remoteClock.sync(timestamp);
        }
    }

    public MiClient getClient() {
        return client;
    }
}
